#include <sstream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>

#include "debug-shell.h"

DebugShell::DebugShell (AVR& avr)
  : _avr (avr)
{
}

void DebugShell::run ()
{
  bool done = false;
  string cmd;
  string line;

  while (!done) {
    cout << endl << "> ";
    getline (cin, line);

    int cend = line.find_first_of (' ');
    if (cend > -1)
      cmd = line.substr (0, cend);
    else
      cmd = line;

    if (cmd == "asm") {
      if (cend == -1) {
        cout << "No instruction given" << endl;
        continue;
      }

      uint16_t inst = assemble_instruction (line.substr (cend + 1));
      if (inst != 0xffff)
        _avr.execute_instruction (inst);
    }
    else if (cmd == "core") {
      _avr.dump_core_status (cout);
      cout << endl;
      _avr.dump_registers (cout);
    }
    else if (cmd == "dmem") {
      string args = line.substr (cend + 1);

      istringstream is (args);
      uint16_t addr;
      uint16_t cnt;

      is >> hex >> addr >> dec >> cnt;

      uint8_t dump[cnt];
      _avr.dump_dmem (dump, addr, cnt);

      print_memory_region<uint8_t> (dump, addr, cnt);
    }
    else if (cmd == "pmem") {
      string args = line.substr (cend + 1);

      istringstream is (args);
      uint16_t addr;
      uint16_t cnt;

      is >> hex >> addr >> dec >> cnt;

      uint16_t dump[cnt];
      _avr.dump_pmem (dump, addr, cnt);

      print_memory_region<uint16_t> (dump, addr, cnt);
    }
    else if (cmd == "quit")
      done = true;
    else if (cmd == "help")
      print_help ();
    else {
      cout << "Unknown command" << endl;
      print_help ();
    }
  }
}

uint16_t DebugShell::assemble_instruction (const string& instruction)
{
  uint16_t ret = 0xffff;

  /* pipefd[0] read, pipefd[1] write */
  int chstdout[2] = { -1, -1 }; /* Child stdout */
  int chstdin[2] = { -1, -1 }; /* Child stdin */

  if (pipe (chstdout) || pipe (chstdin)) {
    cout << "Error creating IPC pipe" << endl;
    return 0xffff;
  }

  char tmpfname[7] = "XXXXXX";
  mkstemp (tmpfname);

  pid_t pid = fork ();
  if (pid == 0) {
    close (chstdin[1]);
    close (chstdout[0]);
    
    if (dup2 (chstdin[0], STDIN_FILENO) == -1
        || dup2 (chstdout[1], STDOUT_FILENO) == -1
        || dup2 (chstdout[1], STDERR_FILENO) == -1) {
      perror ("dup2");
      _exit (1);
    }

    if (execlp ("avr-as", "avr-as", "-o", tmpfname, (char *)0) == -1) {
      perror ("execlp");
      _exit (1);
    }
  }
  else if (pid == -1) {
    cout << "Error forking avr-as" << endl;
    return 0xffff;
  }
  else {
    close (chstdin[0]);
    close (chstdout[1]);

    write (chstdin[1], instruction.c_str (), instruction.size ());
    close (chstdin[1]);

    int cstat;
    wait (&cstat);
    if (WEXITSTATUS (cstat) != 0) {
      char buff[128];
      cout << "Instruction assembly failed:" << endl << endl;

      int nread = 1;
      while (nread > 0) {
        nread = read (chstdout[0], buff, 127);
        buff[nread] = 0;
        cout << buff;
      }
    }
    else {
      if (pipe (chstdout)) {
        cout << "Error creating IPC pipe" << endl;
        return 0xffff;
      }

      pid = fork ();
      if (pid == 0) {
        close (chstdin[1]);
        close (chstdout[0]);
        
        if (dup2 (chstdout[1], STDOUT_FILENO) == -1
            || dup2 (chstdout[1], STDERR_FILENO) == -1) {
          perror ("dup2");
          _exit (1);
        }

        if (execlp ("avr-objcopy", "avr-objcopy", "-O", "binary",
                    tmpfname, (char *)0) == -1) {
          perror ("execlp");
          _exit (1);
        }
      }
      else if (pid == -1) {
      }
      else {
        close (chstdout[1]);

        int cstat;
        wait (&cstat);
        if (WEXITSTATUS (cstat) != 0) {
          char buff[128];
          cout << "Instruction assembly failed:" << endl << endl;

          int nread = 1;
          while (nread > 0) {
            nread = read (chstdout[0], buff, 127);
            buff[nread] = 0;
            cout << buff;
          }
        }
        else {
          ifstream ifile (tmpfname, ios::binary | ios::in);

          ifile.read ((char *)&ret, sizeof (ret));

          ifile.close ();
        }

        unlink (tmpfname);
      }
    }

    close (chstdout[0]);
  }

  return ret;
}

void DebugShell::print_help ()
{
  cout << "Commands:" << endl << endl;
  cout << "  asm <instruction>      execute instruction" << endl;
  cout << "  core                   display core status" << endl;
  cout << "  dmem <address> <count> dump data memory" << endl;
  cout << "  pmem <address> <count> dump program memory" << endl;
  cout << "  help                   print this message" << endl;
  cout << "  quit                   exit shell" << endl;
}
